/**
 * @file Application.cc
 * @author Bono.ASUN.TamGitsun (gitsun.tam@outlook.com)
 * @brief 
 * @version 0.1
 * @date 2022-02-07
 * 
 * @copyright Copyright (c) 2022
 * 
 */


#include "application.hh"
#include <utils/asserts.hh>
#include <utils/time.hh>
#include <utils/logger/Logger.hh>
#include <utils/string.hh>
#include <utils/number.hh>
#include <readers/SerialReader.hh>


#define LOG_TAG "ECTRandomData_Main"
#define THIS_FILE "application.cc"


#define ACK_OK "OK\r\n"

#define CMD_TIMESYNC "AT+TIMESYNC?\r\n"
#define CMD_UPLOAD_START "AT+UPLOADSTART\r\n"


using namespace TGS::utils;
using namespace TGS::readers;


namespace ECT::APPS::RANDOM_DATA {


struct ApplicationData
{
    ApplicationData(void);
    ~ApplicationData(void);
    TGS::readers::IReader * mReader {nullptr};
    bool mDoUpload {false};
};

ApplicationData::ApplicationData(void)
{
    mReader = new SerialReader();
}

ApplicationData::~ApplicationData(void)
{
    delete mReader;
}


const std::string Application::PROPERTY_URI = "Uri";
const std::string Application::PROPERTY_RUN_DURATION = "RunDuration";


Application::Application(void)
{
    mData = new ApplicationData();
}

Application::~Application(void)
{
    delete mData;
}

static int32_t extract_command(ApplicationData * app_data, char * data, int32_t size)
{
    if (!strncmp(data, CMD_TIMESYNC, strlen(CMD_TIMESYNC)))
    {
        app_data->mReader->send((uint8_t *) ACK_OK, (int32_t) strlen(ACK_OK));
        mlog_d(LOG_TAG, THIS_FILE, "Send: %s", ACK_OK);
        char ack[128] = {0};
        snprintf(ack, sizeof(ack), "+TIMESYNC:%llu\r\n", get_time_millisecond());
        app_data->mReader->send((uint8_t *) ack, (int32_t) strlen(ack));
        mlog_d(LOG_TAG, THIS_FILE, "Send: %s", ack);
        return (int32_t) strlen(CMD_TIMESYNC);
    } else if (!strncmp(data, CMD_UPLOAD_START, strlen(CMD_UPLOAD_START)))
    {
        app_data->mReader->send((uint8_t *) ACK_OK, (int32_t) strlen(ACK_OK));
        mlog_d(LOG_TAG, THIS_FILE, "Send: %s", ACK_OK);
        app_data->mDoUpload = true;
        return (int32_t) strlen(CMD_UPLOAD_START);
    }
    return 1;  // Skip one char
}

static void read_main(ApplicationData * app_data, volatile bool * run)
{
    char read_data[1024] = {0};
    char * pw = read_data;
    while (*run)
    {
        if (app_data->mReader->available() > 0)
        {
            uint8_t data[256] = {0};
            int32_t rc = app_data->mReader->read(data, sizeof(data));
            mlog_d(LOG_TAG, THIS_FILE, "Read[%d]: %s", rc, (char *) data);
            if (rc > 0)
            {
                if (pw + rc - read_data > (int32_t) sizeof(read_data))
                {
                    mlog_d(LOG_TAG, THIS_FILE, "Read buffer overflow");
                    rc = (int32_t) sizeof(read_data) - (int32_t) (pw - read_data);
                }
                memcpy(pw, data, rc);
                pw += rc;

                // Extract the commmand:
                rc = extract_command(app_data, read_data, (int32_t) (pw - read_data));
                if (rc > 0)
                {
                    pw -= rc;
                    memmove(read_data, read_data + rc, pw - read_data);
                }
            }
            continue;
        }
        msleep(20);
    }
}

int Application::main_entry(void)
{
    std::string uri = get_property(PROPERTY_URI, "");
    ASSERT_RETURN(uri.length() > 0, -1);
    std::vector<std::string> uri_;
    string_split(uri, ":", uri_);
    ASSERT_RETURN(uri_.size() >= 2, -1);
    mData->mReader->set_property(SerialReader::PROPERTY_DEVICE, uri_[0]);
    mData->mReader->set_property(SerialReader::PROPERTY_BAUD_RATE, uri_[1]);
    mData->mReader->open();

    bool run = true;
    std::thread read_thread = std::thread(read_main, mData, &run);

    int electricity = 0;
    RandomNumber<int> electricity_standard(0, 10);
    RandomNumber<int> electricity_error(-2, 2);

    std::string run_duration = get_property(PROPERTY_RUN_DURATION, "");
    if (run_duration.length() > 0)
    {
        uint64_t run_duration_ = std::stoll(run_duration);
        uint64_t begin = get_time_millisecond();
        while ((get_time_millisecond() - begin) <= run_duration_)
        {
            {
                if (!mData->mDoUpload)
                {
                    msleep(50);
                    continue;
                }
            }
            electricity += electricity_standard.next() + electricity_error.next();
            if (electricity >= 10)
            {
                char at[128];
                snprintf(at, sizeof(at), "+ELEC:%d\r\n", electricity);
                electricity = 0;
                mData->mReader->send((uint8_t *) at, (int32_t) strlen(at));
                mlog_d(LOG_TAG, THIS_FILE, "Send: %s", at);
            }
            msleep(50);
        }
        if (electricity > 0)
        {
            char at[128];
            snprintf(at, sizeof(at), "+ELEC:%d\r\n", electricity);
            electricity = 0;
            mData->mReader->send((uint8_t *) at, (int32_t) strlen(at));
            mlog_d(LOG_TAG, THIS_FILE, "Send: %s", at);
        }
    } else
    {
        while (true)
        {
            {
                if (!mData->mDoUpload)
                {
                    msleep(50);
                    continue;
                }
            }
            electricity += electricity_standard.next() + electricity_error.next();
            if (electricity >= 10)
            {
                char at[128];
                snprintf(at, sizeof(at), "+ELEC:%d\r\n", electricity);
                electricity = 0;
                mData->mReader->send((uint8_t *) at, (int32_t) strlen(at));
                mlog_d(LOG_TAG, THIS_FILE, "Send: %s", at);
            }
            msleep(50);
        }
    }

    run = false;
    if (read_thread.joinable())
    {
        read_thread.join();
    }

    mData->mReader->close();
    return 0;
}


}
